// 联合类型
let arr: (number | string)[] = [1, 'a', 3, 'b']
// 联合类型： 变量可以是两种类型之一
let timer: number | null = null
timer = 2

// 定义类型
type MyArr = (number | string) []
const arr1: MyArr = [1, '1']

// 函数声明
function add1(num1: number, num2: number): number {
    return num1 + num2
}

// 箭头函数
const add2 = (num1: number = 10, num2: number = 20): number => {
    return num1 + num2
}

// 如果什么都不写，此时，add 函数的返回值类型为： void
const add3 = () => {
}
// 这种写法是明确指定函数返回值类型为 void，与上面不指定返回值类型相同
const add4 = (): void => {
}
// 但，如果指定 返回值类型为 undefined，此时，函数体中必须显示的 return undefined 才可以
const add5 = (): undefined => {
    // 此处，返回的 undefined 是 JS 中的一个值
    return undefined
}

// 可选参数：在可选参数名的后面添加 ?（问号）
// 注意：可选参数只能出现在参数列表的最后，也就是说可选参数后面不能再出现必选参数
function mySlice(start?: number, end?: number): void {
    console.log('起始索引：', start, '结束索引：', end)
}

// 对象类型
//使用 {} 来描述对象结构
// 属性采用属性名: 类型的形式，如果是多行，可以省略,
// 方法采用方法名(): 返回值类型的形式
// 可选使用 ?
const goodItem: { name: string, price: number, func: () => string } = {
    name: '手机',
    price: 2000,
    func: function () {
        return '打电话'
    }
}

// 创建类型别名
type Person = {
    name: string,
    age: number
    sayHi(): void
}
// 使用类型别名作为对象的类型：
let person: Person = {
    name: '小花',
    age: 18,
    sayHi() {
    }
}

interface IGoodItem {
    name: string,
    price: number,
    func: () => string
}


const good1: IGoodItem = {
    name: '手表',
    price: 200,
    func: function () {
        return '看时间'
    }
}

// 接口继承
interface Point2D {
    x: number;
    y: number
}

// 继承 Point2D
interface Point3D extends Point2D {
    z: number
}

// 元组是另一种特殊的数组：它确切地包含多少个元素 特定索引对应的类型
let position: [number, number] = [39.5427, 116.2317]

let str1 = 'hello 武汉'
const str2 = 'hello 武汉'
// 变量 str1 的类型为：string
// 变量 str2 的类型为：‘hello 武汉’
// str1 是一个变量(let)，它的值可以是任意字符串，所以类型为:string
// str2 是一个常量(const)，它的值不能变化只能是 ‘hello 武汉’，所以，它的类型为:‘hello 武汉’
// 注意：此处的 ‘Hello TS’，就是一个字面量类型，也就是说某个特定的字符串也可以作为 TS 中的类型

// 使用自定义类型:
type Direction = 'up' | 'down' | 'left' | 'right'
// 参数 direction 的值只能是 up/down/left/right 中的任意一个

// 定义枚举类型
enum Direction1 { Up, Down, Left, Right }

// 使用枚举类型
function changeDirection(direction1: Direction1) {
    console.log(direction1)
}

// 调用函数时，需要应该传入：枚举 Direction 成员的任意一个
// 类似于 JS 中的对象，直接通过 点（.）语法 访问枚举的成员
changeDirection(Direction1.Up)

// type定义的类型是没有值的
type NewType = number | string
// console.log(NewType)  // 输出类型是没有意义的
// 枚举定义的类型是有值的。
// 调用函数时，需要应该传入：枚举 Direction 成员的任意一个
// 类似于 JS 中的对象，直接通过 点（.）语法 访问枚举的成员
console.log(Direction1.Up)

// 数字枚举
enum Direction2 { Up = 2, Down = 4, Left = 8, Right = 16 }

enum Direction3 {
    Up = 'UP',
    Down = 'DOWN',
    Left = 'LEFT',
    Right = 'RIGHT'
}

// any: 任意的。当类型设置为 any 时，就取消了类型的限制。
let obj: any = {x: 0}

// 有时候你会比 TS 更加明确一个值的类型，此时，可以使用类型断言来指定更具体的类型。
// 使用 as 关键字实现类型断言
// 关键字 as 后面的类型是一个更加具体的类型（HTMLAnchorElement 是 HTMLElement 的子类型）
const box = document.getElementById('img') as HTMLImageElement
box.src

// JS 中提供了 typeof 操作符，用来在 JS 中获取数据的类型
console.log(typeof 'Hello world')

// 泛型
function fn<T>(value: T): T {
    return value
}

// Type 的类型就是 number，所以，函数fn参数和返回值的类型也都是 number
const num = fn<number>(10)
const str = fn<string>('a')

// 添加约束
// 比如，要求传入T类型必须要有length属性。
interface ILength {
    length: number
}
// T extends ILength 添加泛型约束
function fn1<T extends ILength>(value: T): T {
    console.log(value.length)
    return value
}
fn1('abc') // Ok
fn1([1, 2, 3]) // Ok

// 多个类型变量
// 泛型的类型变量可以有多个，并且类型变量之间还可以约束(比如，第二个类型变量受第一个类型变量约束)
// 比如，创建一个函数来获取对象中属性的值：
function getProp<Type, Key extends keyof Type>(obj: Type, key: Key) {
    return obj[key]
}
// keyof 关键字接收一个对象类型，生成其键名称(可能是字符串或数字)的联合类型。
// 本示例中 keyof Type 实际上获取的是 person 对象所有键的联合类型，也就是：'name' | 'age'
// 类型变量 Key 受 Type 约束，可以理解为：Key 只能是 Type 所有键中的任意一个，或者说只能访问对象中存在的属性
let person1 = { name: 'jack', age: 18 }
getProp(person1, 'name')
// Type extends object 表示： Type 应该是一个对象类型，如果不是 对象 类型，就会报错
// 如果要用到 对象 类型，应该用 object ，而不是 Object
function getProperty<Type extends object, Key extends keyof Type>(obj: Type, key: Key) {
    return obj[key]
}
getProperty({a:1, b:2}, 'a')

// 接口的类型变量，对接口中所有其他成员可见，也就是接口中所有成员都可以使用类型变量。
// 使用泛型接口时，需要显式指定具体的类型
interface 接口名<类型变量> {

}

// 用来构造(创建)一个类型，将 Type 的所有属性设置为可选。
type Props =  {
    id: string
    children: number[]
}
type PartialProps = Partial<Props>

// Readonly 用来构造一个类型，将 Type 的所有属性都设置为 readonly(只读)。
type Props1 =  {
    id: string
    children: number[]
}
type ReadonlyProps = Readonly<Props1>

// Pick<Type, Keys> 从 Type 中选择一组属性来构造新类型。
type Props2 = {
    id: string
    title: string
    children: number[]
}
type PickProps = Pick<Props2, 'id' | 'title'>
